package com.haibara.aioj.judge.service.impl;

import cn.hutool.json.JSONUtil;
import com.haibara.aioj.common.ErrorCode;
import com.haibara.aioj.exception.BusinessException;
import com.haibara.aioj.judge.codesandbox.CodeSandbox;
import com.haibara.aioj.judge.codesandbox.factory.CodeSandboxFactory;
import com.haibara.aioj.judge.codesandbox.proxy.CodeSandboxProxy;
import com.haibara.aioj.judge.model.ExecuteCodeRequest;
import com.haibara.aioj.judge.model.ExecuteCodeRespond;
import com.haibara.aioj.judge.service.JudgeService;
import com.haibara.aioj.judge.strategy.DefaultJudgeStrategy;
import com.haibara.aioj.judge.strategy.JudgeContext;
import com.haibara.aioj.judge.strategy.JudgeManager;
import com.haibara.aioj.judge.strategy.JudgeStrategy;
import com.haibara.aioj.model.dto.question.JudgeCase;
import com.haibara.aioj.model.dto.question.JudgeConfig;
import com.haibara.aioj.model.dto.questionsubmit.JudgeInfo;
import com.haibara.aioj.model.entity.Question;
import com.haibara.aioj.model.entity.QuestionSubmit;
import com.haibara.aioj.model.entity.User;
import com.haibara.aioj.model.enums.JudgeInfoMessageEnum;
import com.haibara.aioj.model.enums.QuestionSubmitLanguageEnum;
import com.haibara.aioj.model.enums.QuestionSubmitStatusEnum;
import com.haibara.aioj.model.vo.QuestionSubmitVO;
import com.haibara.aioj.model.vo.QuestionSubmitViewVO;
import com.haibara.aioj.service.QuestionService;
import com.haibara.aioj.service.QuestionSubmitService;
import com.haibara.aioj.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.beans.Transient;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.haibara.aioj.constant.RedisKey.pageQuestionSubmitKey;
import static com.haibara.aioj.constant.RedisKey.questionSubmitKey;

@Service
public class JudgeServiceImpl implements JudgeService {

    @Resource
    private QuestionService questionService;
    @Resource
    private QuestionSubmitService questionSubmitService;
    @Resource
    private UserService userService;

    @Resource
    private JudgeManager judgeManager;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Value("${codesandbox.type}")
    private String type;

    @Override
    public QuestionSubmit doJudge(Long questionSubmitId) {
        // 1.传入题目提交id，获取到对应题目、提交信息
        QuestionSubmit questionSubmit = questionSubmitService.getById(questionSubmitId);
        Long questionId = questionSubmit.getQuestionId();
        Question question = questionService.getById(questionId);
        // 2.若题目状态不为等待中，就不重复执行 （幂等性）
        if(!Objects.equals(questionSubmit.getStatus(), QuestionSubmitStatusEnum.WAITING.getValue())){
            return null;
        }
        // 3.修改判题状态为判题中，防止重复执行
        questionSubmit.setStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        questionSubmitService.updateById(questionSubmit);
        // 4.创建Codesandbox实例
        CodeSandboxFactory codeSandboxFactory = new CodeSandboxFactory();
        CodeSandbox codeSandbox = new CodeSandboxProxy(codeSandboxFactory.getCodeSandbox(type));

        String judgeCaseStr = question.getJudgeCase();
        List<JudgeCase> judgeCaseList = JSONUtil.toList(judgeCaseStr,JudgeCase.class);
        String language = questionSubmit.getLanguage();
        String code = questionSubmit.getCode();
        List<String> inputList = judgeCaseList.stream().map(JudgeCase::getInput).collect(Collectors.toList());
        // 5.构建ExecuteCodeRequest，发送给Codesandbox
        ExecuteCodeRequest ecr = ExecuteCodeRequest.builder()
                .inputList(inputList)
                .code(code)
                .questionId(questionId)
                .language(QuestionSubmitLanguageEnum.getEnumByValue(language))
                .build();
        ExecuteCodeRespond executeCodeRespond = codeSandbox.executeCode(ecr);
        // 6.判断运行的结果
        // 若返回的ExecuteCodeRespond中状态为 Error，说明执行有异常，不需要进一步判断结果
        if(executeCodeRespond.getStatus() == ExecuteCodeRespond.ERROR){
            questionSubmit.setId(questionSubmitId);
            // 将状态设置为失败
            questionSubmit.setStatus(QuestionSubmitStatusEnum.FAILED.getValue());
            JudgeInfo judgeInfo = new JudgeInfo();
            // 设置错误信息
            judgeInfo.setMessage(executeCodeRespond.getMessage());
            JudgeConfig judgeConfig = JSONUtil.toBean(question.getJudgeConfig(),JudgeConfig.class);
            // 将时间消耗和空间消耗设置为题目要求的最大值
            judgeInfo.setMemory(Double.valueOf(judgeConfig.getMemoryLimit()));
            judgeInfo.setTime(judgeConfig.getTimeLimit());
            questionSubmit.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
            // 放入Redis
            putRedis(questionSubmit,question,judgeInfo);
            return questionSubmit;
        }
        // 7.若返回的结果执行正常，则构建JudgeContext上下文，用于进一步判断结果是否正确
        JudgeContext judgeContext = new JudgeContext();
        judgeContext.setInputList(inputList);
        judgeContext.setOutputList(executeCodeRespond.getOutputList());
        judgeContext.setJudgeInfo(executeCodeRespond.getJudgeInfo());
        judgeContext.setQuestion(question);
        judgeContext.setJudgeCaseList(judgeCaseList);
        judgeContext.setQuestionSubmit(questionSubmit);
        // 8.执行判断
        JudgeInfo judgeInfo = judgeManager.doJudge(judgeContext);

        // 9.修改数据库中的判题结果
        questionSubmit.setId(questionSubmitId);
        questionSubmit.setStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
        questionSubmit.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
        putRedis(questionSubmit,question,judgeInfo);
        return questionSubmit;
    }

    public void putRedis(QuestionSubmit questionSubmit, Question question, JudgeInfo judgeInfo){
        QuestionSubmitViewVO questionSubmitViewVO = new QuestionSubmitViewVO();
        questionSubmitViewVO.setQuestionSubmitId(questionSubmit.getId());
        questionSubmitViewVO.setQuestionId(questionSubmit.getQuestionId());
        questionSubmitViewVO.setQuestionTitle(question.getTitle());
        questionSubmitViewVO.setLanguage(questionSubmit.getLanguage());
        questionSubmitViewVO.setJudgeInfo(questionSubmit.getJudgeInfo());
        User byId = userService.getById(questionSubmit.getUserId());
        questionSubmitViewVO.setUserName(byId.getUserName());
        questionSubmitViewVO.setCreateTime(questionSubmit.getCreateTime());
        boolean b = questionSubmitService.updateById(questionSubmit);
        if(!b){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"数据更新失败");
        }
        if(JudgeInfoMessageEnum.ACCEPTED.getValue().equals(judgeInfo.getMessage())){
            question.setAcceptedNum(question.getAcceptedNum()+1);
            questionService.updateById(question);
        }
        stringRedisTemplate.opsForValue().append(questionSubmitKey+questionSubmit.getId().toString(),JSONUtil.toJsonStr(questionSubmitViewVO));
        stringRedisTemplate.opsForZSet().add(pageQuestionSubmitKey,questionSubmit.getId().toString(),questionSubmit.getCreateTime().getTime());
    }
}
